﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks.Dataflow;
using System.IO;
using System.Diagnostics;
using Sustainalytics.AwsS3.Client;
using Sustainalytics.Entities;
using Sustainalytics.Utils;
using Sustainalytics.Utils.Configuration;
using System.Linq;

namespace Sustainalytics.Governance.Reporting
{
    public class ReportsManager : IReportsManager
    {
        private readonly int _bulkSize;
        private readonly string _dwConnectionString;
        private readonly string _inputFileConnectionString;
        private readonly ConcurrentQueue<string> _log;
        private readonly string _databaseGroup;
        private readonly S3ClientSettings _s3ClientSettings;
        private readonly string _bucketName;
        private readonly string _path;

        private CancellationTokenSource _tokenSource;

        public double Progress { get; set; }

        public string[] Log => _log.ToArray();

        public ReportsManager()
        {
            _log = new ConcurrentQueue<string>();
        }

        public ReportsManager(int bulkSize, IReadOnlyDictionary<string, string> connectionStrings, string databaseGroup)
        {
            _bulkSize = bulkSize;
            _log = new ConcurrentQueue<string>();
            _databaseGroup = databaseGroup;

            if (!connectionStrings.ContainsKey("GovernanceDW"))
                throw new ArgumentException("GovernanceDW key is missing!");
            _dwConnectionString = connectionStrings["GovernanceDW"];

            if (!connectionStrings.ContainsKey("GovernanceInputFile"))
                throw new ArgumentException("GovernanceInputFile key is missing!");
            _inputFileConnectionString = connectionStrings["GovernanceInputFile"];

            _s3ClientSettings = new S3ClientSettings
            {
                AccessKey = SettingsHelper.Read("accesKeyId", "AKIAJKGZZMOFAVOVLHJA"),
                SecretAccessKey = SettingsHelper.Read("awsSecretKey", "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"),
                ServiceEndPoint = SettingsHelper.Read("s3ServiceEndPoint", "https://s3-eu-west-1.amazonaws.com/")
            };

            _bucketName = SettingsHelper.Read("s3bucket", "dev-globalaccess-europe");
            _path = $"E:\\{ReportType.CorporateGovernanceCompanyReport}_{databaseGroup}";
        }

        public static ReportsManager GetInstance(int bulkSize, Dictionary<string, string> connectionStrings, string databaseGroup)
        {
            return new ReportsManager(bulkSize, connectionStrings, databaseGroup);
        }


        public void CancelPdfReportsGeneration()
        {
            if (_tokenSource != null)
                _tokenSource.Cancel();

            _log.Enqueue("Governance Reports generation was canceled.");
        }

        private bool GenerateCompanyReport(ReportDataSource ds)
        {
            try
            {
                var generator = new CompanyReportGenerator();


                var result = generator.GeneratePdfReport(ds);

                if (result.Name == null) return false;

                var pdfReport = new GovernancePdfReport
                {
                    Id = result.Id,
                    FileName = result.Name,
                    FileContent = result.PdfDocument,
                    CreationTimestamp = DateTime.UtcNow
                };

                pdfReport.FileName = ReportName.CG.GetFullReportName(ds.CompanyProfile.CompanyName, result.Id);

                S3Write(pdfReport);
                //SavePdfReport(pdfReport.FileContent, pdfReport.FileName);

                _log.Enqueue(DateTime.UtcNow.ToString("T")+ $" {ds.CompanyProfile.CompanyName} company report was generated and saved successfully. Company ID: {ds.CompanyProfile.Id}");

                return true;
            }
            catch (Exception ex)
            {
                _log.Enqueue(DateTime.UtcNow.ToString("T")+ $":Faild to generate pdf report for company {ds.CompanyProfile.CompanyName} with ID: {ds.CompanyProfile.Id}. Error: {ex}");
                return false;
            }
        }

        private void S3Write(IFileContent report)
        {
            var fullPath = Path.Combine(_path, report.FileName);

            var file = $"{Path.GetFileName(Path.GetDirectoryName(fullPath))}/{report.FileName}";

            _log.Enqueue(DateTime.UtcNow.ToString("T")+$":Uploading file {file} in S3 {_bucketName} bucket");

            try
            {
                using (var s3Client = new S3Client(_s3ClientSettings))
                {
                    s3Client.UploadFile(_bucketName, file, report.FileContent);
                }
            }
            catch (Exception ex)
            {
                _log.Enqueue(DateTime.UtcNow.ToString("T")+$":Uploading file {file} in S3 failed. Error:{ex}");
            }
        }

        private void SavePdfReport(byte[] file, string fileName)
        {
            if (!Directory.Exists(@"E:\Reports\"))
                Directory.CreateDirectory(@"E:\Reports\");

            File.WriteAllBytes(string.Format(@"E:\Reports\{0}", @fileName), file);
        }

        private void LoadCompanyReportData(ITargetBlock<ReportDataSource> target, IEnumerable<Guid> companyIds)
        {
            var reportDataProvider = new ReportDataProvider(_bulkSize, _tokenSource.Token, _dwConnectionString, _inputFileConnectionString);

            while (true)
            {
                var dataSourceObjects = reportDataProvider.GetCompanyReportData();

                if (dataSourceObjects.Length <= 0) break;

                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.WriteLine("{0} objects retrieved", dataSourceObjects.Length);
                Console.ResetColor();

                var dataSourceObjectsFiltered = companyIds.Any() ? dataSourceObjects.Where(a => companyIds.Contains(a.CompanyId)) : dataSourceObjects;

                foreach (var ds in dataSourceObjectsFiltered)
                {
                    target.SendAsync(ds).Wait();
                }
            }
        }

        public bool GeneratePdfReport(Guid companyId)
        {
            var tokenSource = new CancellationTokenSource();

            var reportDataProvider = new ReportDataProvider(_bulkSize, tokenSource.Token, _dwConnectionString, _inputFileConnectionString);

            var dataSourceObject = reportDataProvider.GetCompanyReportData(companyId);

            var result = GenerateCompanyReport(dataSourceObject[0]);

            return result;
        }

        public void GenerateBulkPdfReports(IEnumerable<Guid> companyIds)
        {
            _tokenSource = new CancellationTokenSource();

            RemovePreviousCompanyReportPdfs();

            Progress = 0.0;

            var processorCount = Environment.ProcessorCount;
            var maxDegreeOfParallelism = processorCount / 2;
            var boundedCapacity = processorCount*2;

            var bufferBlock = new BufferBlock<ReportDataSource>(new DataflowBlockOptions
                {
                    BoundedCapacity = boundedCapacity,
                    CancellationToken = _tokenSource.Token
                });

            var generator = new ActionBlock<ReportDataSource>(dataSource => GenerateCompanyReport(dataSource),
                new ExecutionDataflowBlockOptions
                {
                    BoundedCapacity = boundedCapacity,
                    MaxDegreeOfParallelism = maxDegreeOfParallelism,
                    CancellationToken = _tokenSource.Token
                });

            bufferBlock.LinkTo(generator, new DataflowLinkOptions { PropagateCompletion = true });
            bufferBlock.Completion.ContinueWith(task =>
            {
                Console.WriteLine("The status of the completion task is '{0}'.", task.Status);
            });

            LoadCompanyReportData(bufferBlock, companyIds);

            bufferBlock.Complete();
            generator.Completion.Wait();

            Progress = 1;
        }


        private void RemovePreviousCompanyReportPdfs()
        {
            try
            {
                var sw = Stopwatch.StartNew();

                using (var s3Client = new S3Client(_s3ClientSettings))
                {
                    var folderToDelete = string.Concat(ReportType.CorporateGovernanceCompanyReport, _databaseGroup);

                    _log.Enqueue($"S3 Cleanup folder {folderToDelete}");

                    s3Client.DeleteBulk(_bucketName, folderToDelete);
                }

                sw.Stop();
                _log.Enqueue($"Removing of company reports {sw.ElapsedMilliseconds / 1000} [sec]");
            }
            catch (Exception ex)
            {
                _log.Enqueue($"S3 Cleanup exception: {ex.Message}. Stack trace {ex.StackTrace}");
            }
        }

    }
}
